AOP+自定义注解实现的多数据库事务管理(业务场景严谨性极高的情况下请慎用,该方式无法保证绝对的数据一致性,最下面有相关解释说明) 您所在的位置:网站首页 数据库写入失败 1117 AOP+自定义注解实现的多数据库事务管理(业务场景严谨性极高的情况下请慎用,该方式无法保证绝对的数据一致性,最下面有相关解释说明)

AOP+自定义注解实现的多数据库事务管理(业务场景严谨性极高的情况下请慎用,该方式无法保证绝对的数据一致性,最下面有相关解释说明)

2024-02-24 22:23| 来源: 网络整理| 查看: 265

d000baa1cd11728bfc28c72866d11cc5c3fd2c68.webp

场景与方案

当一个服务,连接两个数据库,一次接口请求需要向两个数据库同时写入数据,我们使用 @Transactional 注解方式加入事务,此时A数据库写入成功,B数据库写入失败,这时可能数据库A写入的数据不能回滚,原因是 @Transactional 注解中 transactionManager 的参数决定的,只能指定一个事务管理。

A数据源配置代码

@Configuration @MapperScan(basePackages = "com.p.test.testadb.mapper", annotationClass = Repository.class, sqlSessionFactoryRef = DataSourceConfig.SQL_SESSION_FACTORY_NAME) public class DataSourceAConfig { static final String SQL_SESSION_FACTORY_NAME = "aSessionFactory"; public static final String A_TX_MANAGER = "aTxManager"; @Bean(name = "aDataSource") @ConfigurationProperties(prefix = "spring.datasource") @Primary public DataSource dataSource() { return DataSourceBuilder.create().build(); } @Bean(name = A_TX_MANAGER) @Primary public PlatformTransactionManager txManager() { return new DataSourceTransactionManager(dataSource()); } @Bean(name = SQL_SESSION_FACTORY_NAME) @Primary public SqlSessionFactory sqlSessionFactoryBean() throws Exception { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources("classpath*:mapper-testadb/*.xml")); sqlSessionFactoryBean.setDataSource(dataSource()); org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration(); configuration.setCacheEnabled(false); sqlSessionFactoryBean.setConfiguration(configuration); return sqlSessionFactoryBean.getObject(); } }

B数据源配置代码

@Configuration @MapperScan(basePackages = "com.p.test.testbdb.mapper", annotationClass = Repository.class, sqlSessionFactoryRef = DataSourceConfig.SQL_SESSION_FACTORY_NAME) public class DataSourceBConfig { static final String SQL_SESSION_FACTORY_NAME = "bSessionFactory"; public static final String B_TX_MANAGER = "bTxManager"; @Bean(name = "bDataSource") @ConfigurationProperties(prefix = "spring.datasource") @Primary public DataSource dataSource() { return DataSourceBuilder.create().build(); } @Bean(name = B_TX_MANAGER) @Primary public PlatformTransactionManager txManager() { return new DataSourceTransactionManager(dataSource()); } @Bean(name = SQL_SESSION_FACTORY_NAME) @Primary public SqlSessionFactory sqlSessionFactoryBean() throws Exception { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources("classpath*:mapper-testbdb/*.xml")); sqlSessionFactoryBean.setDataSource(dataSource()); org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration(); configuration.setCacheEnabled(false); sqlSessionFactoryBean.setConfiguration(configuration); return sqlSessionFactoryBean.getObject(); } }

最原始的办法

public interface TestService { int insertAAndB(); } @Service public class TestServiceImpl implements TestService { @Autowired private Test1Service test1Service; @Override @Transactional(DataSourceAConfig.A_TX_MANAGER) public int insertAAndBUser() { return test1Service.insert(); } } public interface Test1Service { int insert(); } @Service public class Test1ServiceImpl implements Test1Service { @Override @Transactional(transactionManager = DataSourceBConfig.B_TX_MANAGER) public int insert() { //a写入库 a.insert(); //b写入库 b.insert(); return 1; } }

这种方式如果我们一次操作10个数据库,我们就要手写嵌套10层方法,简直是让人头疼

最终解决方案

AOP+自定义注解,下面直接开始撸码

/** * @Author: pp * @DateTime: 2022/3/29 14:33 * @Description: 定义多事务注解 */ import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MultiTransactional { String[] value() default {}; }

下面是AOP相关代码,通过AOP技术拦截所有使用了 @MultiTransactional 注解的方法

/** * @Author: pp * @DateTime: 2022/3/29 14:36 * @Description: AOP拦截MultiTransactional注解方法 */ @Aspect @Component public class MultiTransactionAop { private final ComboTransaction comboTransaction; @Autowired public MultiTransactionAop(ComboTransaction comboTransaction) { this.comboTransaction = comboTransaction; } @Pointcut("@annotation(com.modian.chain.config.datasource.annotation.MultiTransactional)") public void pointCut() { } @Around("pointCut() && @annotation(multiTransactional)") public Object inMultiTransaction(ProceedingJoinPoint pjp, MultiTransactional multiTransactional) { return comboTransaction.inCombinedTx(() -> { try { return pjp.proceed(); } catch (Throwable throwable) { if (throwable instanceof RuntimeException) { throw (RuntimeException) throwable; } throw new RuntimeException(throwable); } }, multiTransactional.value()); } }

下面实现一个公共事务方法,这代码很简单,将注解中的参数带过来,循环参数中的事务,判断是哪个事务就执行哪个事务对应的的方法

/** * @Author: pp * @DateTime: 2022/3/29 14:38 * @Description: 公共事务实现 */ @Component public class ComboTransaction { @Autowired private ATxBroker aTxBroker; @Autowired private BTxBroker bTxBroker; public V inCombinedTx(Callable callable, String[] transactions) { if (callable == null) { return null; } Callable combined = Stream .of(transactions) .filter(ele -> !StringUtils.isEmpty(ele)) .distinct() .reduce(callable, (r, tx) -> { switch (tx) { case DataSourceBusinessConfig.A_TX_MANAGER: return () -> aTxBroker.inTransaction(r); case DataSourceChainConfig.B_TX_MANAGER: return () -> aTxBroker.inTransaction(r); default: return null; } }, (r1, r2) -> r2); try { return combined.call(); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException(e); } } }

ATxBroker和BTxBroker的实现,下面这个代码应该很眼熟吧,和上面我说的很low的实现思想是一样的,只不过这里我们统一封装了而已

/** * @Author: pp * @DateTime: 2022/3/29 14:39 * @Description: 单个事务实现 */ @Component public class ATxBroker { @Transactional(DataSourceAConfig.A_TX_MANAGER) public V inTransaction(Callable callable){ try { return callable.call(); }catch (RuntimeException e){ throw e; }catch (Exception e){ throw new RuntimeException(e); } } } /** * @Author: pp * @DateTime: 2022/3/29 14:39 * @Description: 单个事务实现 */ @Component public class BTxBroker { @Transactional(DataSourceBConfig.B_TX_MANAGER) public V inTransaction(Callable callable){ try { return callable.call(); }catch (RuntimeException e){ throw e; }catch (Exception e){ throw new RuntimeException(e); } } }

使用方式如下

@Component public class ABService{ @MultiTransactional(value = {DataSourceBusinessConfig.A_TX_MANAGER, DataSourceChainConfig.B_TX_MANAGER}) public void add(){ //A库写入数据 a.add(); //B库写入数据 b.add(); } }

重点(必读)

如果A事务已经commit后,B事务准备commit,但网络等原因导致问题B库commit失败,那么A库则无法回滚,需要考虑使用场景,如果出现类似问题需要做后续的数据处理



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有